<template>
    <div class="scroller-wrapper" ref="wrapper">
		<div class="content-wrapper" ref="slot" @scroll="onScrollHandle" @mouseenter="showBar" @mouseleave="yLeaveHandle">
			<slot></slot>
		</div>
		<div class="y-bar-outer bar" ref="ybar">
			<div class="inner"
				@mousedown="yMouseDownHandle" 
				:class="{move:move.yMouseDown}" 
				:style="`height:${yBarHeight}px;opacity:${yBarOpa};transform:translateY(${yInnerTop}px)`"></div>
		</div>
	</div>
</template>

<script>
import { getScrollBarSize, debounce, isMobile } from "../lib/scroll"
import { reactive, ref, toRefs, onMounted, watch, nextTick } from 'vue'
export default {
    name: 'ScrollBar',
    props: {
        autoHide: {   //滚动条自动隐藏
            type: Boolean,
            default: false
        },
        forData: {
            type: [Array,Number],
            default: ()  => {
                return []
            }
        },
        ScrollTop:{
            type: Number,
            default: 0
        },
        listenScrollBottom: {
            type: [Function, Boolean],
            default: ()=>{}
        },
        listenScrollTop:{
            type: [Function, Boolean],
            default: ()=>{}
        },
        listenScroll:{
            type: [Function, Boolean],
            default: ()=>{}
        }
    },
    setup(props) {
        let { autoHide, listenScrollBottom, listenScrollTop, listenScroll, ScrollTop, forData } = toRefs(props)
        const slot = ref(null)
        const wrapper = ref(null)
        const ybar = ref(null)
        let yBarHeight = ref(0) //滑块高度
        let yBarOpa = ref(1) //滑块透明度
        let yInnerTop = ref(0)
        const state = reactive({
            info: {
                // 刷新的时候才更新 减少读取 增加性能
                wrapperHeight: 0,   //外容器高度
                contentHeight: 0,   //内部总高度
                yBarHeight: 0       //滑轨高度
            },
            move: {
                yMouseDown: false,
                yMouseDownOffset: 0
            }  
        })
        // 节流
        const debounceHide = debounce(() => {
            // 放置在此处的判断是个妥协
            // 尽管会可读性 但是这个判断过于重要 几乎所有地方都需要达成条件才触发自动隐藏
            // 并且在外部判断 由于延时的特性会导致一些同步问题
            if(autoHide && !state.move.yMouseDown.value) {
                hideBar();
            }
        }, 1000);
        const hideBar = () => {
            yBarOpa.value = 0
        }
        const showBar = () => {
            yBarOpa.value = 1
        }
        const refresh = () => {
            setContentOffset(); //-17px
            reGetInfo(); //获取滚动条信息
            if(state.info.contentHeight <= state.info.wrapperHeight + 1) {
                yBarHeight.value = 0;
            } else {
                yBarHeight.value = (state.info.wrapperHeight / state.info.contentHeight) * state.info.yBarHeight;   //高度百分比=>滑块高度
            }
            onScrollHandle();   //更新滑块top
		}
        const setContentOffset = () => {
            // 获取该设备的默认滚动条宽度 依赖这个宽度把滚动条顶出去
            let scrollBarWidth = isMobile() ? getScrollBarSize() : getScrollBarSize();
            slot.value.style.marginRight = `${-scrollBarWidth}px`;
            slot.value.style.marginBottom = `${0}px`;
        }
        const reGetInfo = () => {
            // 获取基本的宽高信息
            state.info.wrapperHeight = wrapper.value.clientHeight;
            state.info.contentHeight = slot.value.children[0].clientHeight;
            state.info.yBarHeight = ybar.value.clientHeight;
        }
        // 自动隐藏
        const onScrollHandle = () => {
            // 自动隐藏
            showBar();
            debounceHide();
            const slotScrollTop = slot.value.scrollTop;
            const yScrollPercent = slotScrollTop / state.info.contentHeight;  //计算scrollTop所占容器高度百分百
            yInnerTop.value = state.info.yBarHeight * yScrollPercent;   //滑块top
            listenScroll.value(slotScrollTop);
            // 监听滚动到底部
            if(listenScrollBottom) {
                const bottomHeight = state.info.contentHeight - state.info.wrapperHeight - slotScrollTop;
                if(bottomHeight <= 0.5) {
                    listenScrollBottom.value();
                }
            }
            if(listenScrollTop.value) {
                if(slotScrollTop == 0) {
                    listenScrollTop.value();
                }
            }
        }
        const yMouseDownHandle = (e) => {
            const eventScreenY = e.screenY;
            state.move.yMouseDown = true;
            state.move.yMouseDownOffset = eventScreenY;
            
            showBar();
            
            const moveHandle = e => {
                e.preventDefault();
                // 计算出Y轴偏移和缩放比例 让content进行滚动
                const moveScreenY = e.screenY;
                let offsetY = moveScreenY - state.move.yMouseDownOffset;
                state.move.yMouseDownOffset = moveScreenY;
                let scale = state.info.contentHeight / state.info.yBarHeight;
                let contentOffset = offsetY * scale;
                slot.value.scrollTop += contentOffset;
            };
            
            document.addEventListener("mousemove", moveHandle);
            
            document.addEventListener("mouseup", () => {
                // 清除监听 重设默认值
                // 自动隐藏
                document.removeEventListener("mousemove", moveHandle);
                state.move.yMouseDown = false;
                state.move.yMouseDownOffset = 0;
                debounceHide();
            });
        }
        const yLeaveHandle = () => {
            if(!state.move.yMouseDown) {
                debounceHide();
            }
        }
        watch(() => forData,
        () => {
            nextTick(()=>{
                refresh();
            })
        })
        watch(() => ScrollTop.value,
            (oldVal, newVal) => {
                slot.value.scrollTop = newVal || oldVal;
            },
            {
                deep: true
            }
        )
        onMounted(() => {
            debounceHide()
            if(autoHide) {
                showBar();
				debounceHide();
            }
            nextTick(()=>{
                refresh();
            })
        })
        return {
            ...state,
            debounceHide,
            slot,
            wrapper,
            ybar,
            yMouseDownHandle,
            showBar,
            onScrollHandle,
            yLeaveHandle,
            yBarHeight,
            yBarOpa,
            yInnerTop
        }
    }
}
</script>

<style scoped lang="scss">
.scroller-wrapper {
    position: absolute;
    top: 0px;
    left: 0px;
    right: 0px;
    bottom: 0px;
    overflow: hidden;
}
.content-wrapper {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    margin-right: -7px;
    margin-bottom: -7px;
    overflow: scroll;
    overflow-x: hidden;
}
.y-bar-outer {
    position: absolute;
    width: 6px;
    transition: opacity 200ms ease 0s;
    opacity: 1;
    right: 0px;
    bottom: 0px;
    top: 0px;
    border-radius: 0px;
}
.inner {
    position: relative;
    display: block;
    width: 100%;
    cursor: pointer;
    border-radius: 16px;
    background-color: rgba(0,0,0,.15);
    transition: opacity 0.4s ease,height .6s ease;
}
.bar .inner:hover {
    background-color: rgba(0,0,0,.4);  
}
.bar .inner.move {
    background-color: rgba(0,0,0,.4);
}
</style>